/*
 * Copyright 2010 Chad Retz
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package org.jdocng.shared.model;

import java.lang.reflect.Modifier;

import javax.xml.bind.annotation.XmlType;

import com.google.common.annotations.GwtCompatible;
import com.google.gson.annotations.Expose;
import com.sun.javadoc.MethodDoc;
import com.sun.javadoc.Type;

/**
 * Implementation of {@link MethodDoc}
 *
 * @author Chad Retz
 */
@XmlType(name = "method")
@GwtCompatible(serializable = true)
@SuppressWarnings("serial")
public class MethodDocImpl extends ExecutableMemberDocImpl implements MethodDoc {

    //exposed fields
    
    @Expose
    private String overriddenClass;

    @Expose
    private String returnType;
    
    //non-exposed fields
    
    private transient ClassDocImpl overriddenClassLoaded;
    
    private transient MethodDocImpl overriddenMethodLoaded;
    
    private transient Type overriddenTypeLoaded;
    
    private transient Type returnTypeLoaded;
    
    //MethodDoc interface impl
    
    @Override
    public boolean isAbstract() {
        return Modifier.isAbstract(modifierSpecifier());
    }
    
    @Override
    public ClassDocImpl overriddenClass() {
        if (overriddenClass == null) {
            return null;
        }
        if (overriddenClassLoaded == null) {
            overriddenClassLoaded = getContext().getClassDoc(overriddenClass);
        }
        return overriddenClassLoaded;
    }
    
    @Override
    public MethodDocImpl overriddenMethod() {
        if (overriddenClass == null) {
            return null;
        }
        if (overriddenMethodLoaded == null) {
            if (overriddenClass() != null) {
                for (MethodDocImpl method : overriddenClass().methods()) {
                    if (name().equals(method.name()) &&
                            returnType().equals(method.returnType()) &&
                            parameters().length == method.parameters().length) {
                        boolean found = true;
                        for (int i = 0; i < parameters().length; i++) {
                            if (!parameters()[i].type().equals(
                                    method.parameters()[i].type())) {
                                found = false;
                                break;
                            }
                        }
                        if (found) {
                            overriddenMethodLoaded = method;
                            break;
                        }
                    }
                }
            }
        }
        return overriddenMethodLoaded;
    }
    
    @Override
    public Type overriddenType() {
        if (overriddenClass == null) {
            return null;
        }
        if (overriddenTypeLoaded == null) {
            overriddenTypeLoaded = getContext().getType(overriddenClass);
        }
        return overriddenTypeLoaded;
    }
    
    @Override
    public boolean overrides(MethodDoc meth) {
        throw new UnsupportedOperationException("Not done yet, still thinkin");
    }
    
    @Override
    public Type returnType() {
        if (returnTypeLoaded == null) {
            returnTypeLoaded = getContext().getType(returnType);
        }
        return returnTypeLoaded;
    }
    
    //other accessors
    
    public String getOverriddenClass() {
        return overriddenClass;
    }
    
    public void setOverriddenClass(String overriddenClass) {
        this.overriddenClass = overriddenClass;
    }
    
    public String getReturnType() {
        return returnType;
    }
    
    public void setReturnType(String returnType) {
        this.returnType = returnType;
    }
    
    //helper methods
    
    boolean isSerializationMethod() {
        //writeObject, readObject, writeReplace, readResolve
        if ("writeObject".equals(name()) && 
                isPrivate() && parameters().length == 1 &&
                "java.io.ObjectOutputStream".equals(parameters()[0].typeName()) &&
                "void".equals(returnType) &&
                thrownExceptions().length == 1 &&
                "java.io.IOException".equals(thrownExceptions()[0].qualifiedName())) {
            return true;
        } else if ("readObject".equals(name()) && 
                isPrivate() && parameters().length == 1 &&
                "java.io.ObjectInputStream".equals(parameters()[0].typeName()) &&
                "void".equals(returnType) &&
                thrownExceptions().length == 2 &&
                "java.io.IOException".equals(thrownExceptions()[0].qualifiedName()) &&
                "java.lang.ClassNotFoundException".equals(thrownExceptions()[0].qualifiedName())) {
            return true;
        } else if (("writeReplace".equals(name()) || "readResolve".equals(name())) && 
                parameters().length == 0 &&
                "java.lang.Object".equals(returnType) &&
                thrownExceptions().length == 1 &&
                "java.io.ObjectStreamException".equals(thrownExceptions()[0].qualifiedName())) {
            return true;
        }
        return false;
    }
}
